/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.ppm.gtm.mas;

import bgu.ppm.sim.api.exec.TickBasedExecutable;
import bgu.ppm.sim.api.exec.TickBasedExecutionSystem;
import bgu.ppm.sim.api.mas.MultiAgentSystem;
import bgu.sonar.util.evt.EventListeners;
import bgu.sonar.util.reflections.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 *
 * @author User
 */
public class TurnBasedExecutionSystem implements TickBasedExecutionSystem {

    private ReadWriteLock executionListLock = new ReentrantReadWriteLock();
    private ArrayList<TickBasedExecutable> executionList = new ArrayList<>();
    private boolean terminated = false;
    @Inject(type = MultiAgentSystem.class)
    private MultiAgentSystem mas;
    private EventListeners<ExecutionListener> listeners = EventListeners.create(ExecutionListener.class);

    @Override
    public void addAll(Collection<? extends TickBasedExecutable> executables) {
        executionListLock.writeLock().lock();
        try {
            for (TickBasedExecutable e : executables){
                add(e);
            }
        } finally {
            executionListLock.writeLock().unlock();
        }
    }

    @Override
    public void removeAll(Collection<TickBasedExecutable> executables) {
        executionListLock.writeLock().lock();
        try {
            for (TickBasedExecutable e : executables){
                remove(e);
            }
        } finally {
            executionListLock.writeLock().unlock();
        }
    }

    @Override
    public void add(TickBasedExecutable exacutables) {
        executionListLock.writeLock().lock();
        try {
            listeners.fire().beforeExecutableAdded(this, exacutables);
            executionList.add(exacutables);
            listeners.fire().afterExecutableAdded(this, exacutables);
        } finally {
            executionListLock.writeLock().unlock();
        }
    }

    @Override
    public void remove(TickBasedExecutable exacutables) {
        executionListLock.writeLock().lock();
        try {
            listeners.fire().beforeExecutableRemoved(this, exacutables);
            executionList.remove(exacutables);
            listeners.fire().afterExecutableRemoved(this, exacutables);
        } finally {
            executionListLock.writeLock().unlock();
        }
    }

    @Override
    public void stop() {
        terminated = true;
    }

    @Override
    public EventListeners<ExecutionListener> getListeners() {
        return listeners;
    }

    @Override
    public void run() {
        List<TickBasedExecutable> removeList = new LinkedList<>();

        terminated = false;

        while (!terminated) {
            executionListLock.readLock().lock();
            try {
                for (Iterator<TickBasedExecutable> it = executionList.iterator(); it.hasNext() && !terminated;) {
                    TickBasedExecutable e = it.next();
                    switch (e.getState()) {
                        case UNINITIALIZED:
                            e.initialize(mas.getGlobalModel());
                            break;
                        case INITIALIZED:
                        case RUNNING:
                            listeners.fire().beforeTick(this, e);
                            e.tick();
                            listeners.fire().afterTick(this, e);
                            break;
                        case TERMINATED:
                        case CRUSHED:
                            removeList.add(e);
                            break;
                        default:
                            throw new AssertionError(e.getState().name());
                    }
                }
            } finally {
                executionListLock.readLock().unlock();
            }

            if (!removeList.isEmpty()) {
                removeAll(removeList);
                listeners.fire().onExecutablesEnded(this, removeList);
                removeList.clear();
            }
            
            if (!terminated && executionList.isEmpty()) {
                throw new RuntimeException("Execution list is empty but execution is not terminated");
            }
        }
    }

    @Override
    public void initialize(MultiAgentSystem system) {
    }
}
